﻿Imports System.Linq.Expressions
Imports System.Runtime.CompilerServices
Imports System.Reflection

Namespace Load

    ''' <summary>
    ''' QueryableList拡張メソッド
    ''' </summary>
    ''' <remarks></remarks>
    Public Module QueryableListExtension

#Region "QueryableList"
        ''' <summary>
        ''' 親を読み込みます
        ''' </summary>
        ''' <typeparam name="MyT">自身の型</typeparam>
        ''' <typeparam name="ParentT">親の型</typeparam>
        ''' <param name="source">読み込み起点となるインスタンス</param>
        ''' <param name="parentCallback">コールバック関数</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        <Extension()>
        Public Function LoadParent(Of MyT, ParentT)(source As QueryableList(Of MyT), Optional parentCallback As Action(Of IList) = Nothing) As QueryableList(Of MyT)
            '検索条件を動的に作成
            Dim parentProps As List(Of PropertyInfo) = (From el In source.InnerList.GetType.GetGenericArguments.First.GetProperties Where el.PropertyType.Equals(GetType(ParentT))).ToList
            Dim q As Query(Of ParentT) = CreateParentQuery(Of MyT, ParentT)(source, parentProps)

            'マッピング実行
            Dim lst As List(Of ParentT) = q.ToList

            '結合
            For Each prop As PropertyInfo In parentProps
                PropertyWriter.SetParentOrNothing(Of MyT, ParentT)(source.InnerList, lst, prop.Name)
            Next

            '親リストのコールバック
            If parentCallback IsNot Nothing Then parentCallback.Invoke(lst)

            '返却
            Return source
        End Function

        ''' <summary>
        ''' 親を読み込むクエリーを生成します
        ''' </summary>
        ''' <typeparam name="MyT">自身の型</typeparam>
        ''' <typeparam name="ParentT">親の型</typeparam>
        ''' <param name="source">読み込み起点となるインスタンス</param>
        ''' <param name="parentProps">親プロパティ</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function CreateParentQuery(Of MyT, ParentT)(source As QueryableList(Of MyT), parentProps As List(Of PropertyInfo)) As Query(Of ParentT)
            Dim q As Query(Of ParentT) = Nothing

            If parentProps.Count = 1 Then
                '指定された型を持つプロパティは1つ
                q = CreateParentQuery_Single(Of MyT, ParentT)(source, parentProps.First)
            Else
                '指定された型を持つプロパティは複数
                q = CreateParentQuery_Multi(Of MyT, ParentT)(source, parentProps)
            End If

            Return q
        End Function

        ''' <summary>
        ''' 親を読み込みクエリーを生成します
        ''' </summary>
        ''' <typeparam name="MyT">自身の型</typeparam>
        ''' <typeparam name="ParentT">親の型</typeparam>
        ''' <param name="source">読み込み起点となるインスタンス</param>
        ''' <param name="prop">親プロパティ</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function CreateParentQuery_Single(Of MyT, ParentT)(source As QueryableList(Of MyT), prop As PropertyInfo) As Query(Of ParentT)
            Dim q As Query(Of ParentT) = source.From(Of ParentT)()

            Dim columns As List(Of ParentRelationColumnAttribute) = (From el In GetType(MyT).ToTable.Columns Where el.IsParent AndAlso el.PropertyInfo.Equals(prop) Select DirectCast(el, ParentRelationColumnAttribute)).ToList
            If columns.Count = 1 Then
                '結合条件が1つ
                Dim col As ParentRelationColumnAttribute = columns.First
                q.WhereInOrEqual(col.YourColumn.PropertyInfo, (From el In source.InnerList Select col.GetValue(el) Distinct).ToArray)
            Else
                '結合条件が複数
                Dim fn As Func(Of MyT, Query(Of ParentT)) =
                    Function(x)
                        Dim subq As Query(Of ParentT) = source.From(Of ParentT)()
                        For Each col As ParentRelationColumnAttribute In columns
                            subq.WhereEquals(col.YourColumn.PropertyInfo, col.GetValue(x))
                        Next
                        Return subq
                    End Function

                q.WhereOR((From el In source.InnerList Select fn.Invoke(el)).ToArray)
            End If

            Return q
        End Function

        ''' <summary>
        ''' 親を読み込みクエリーを生成します
        ''' </summary>
        ''' <typeparam name="MyT">自身の型</typeparam>
        ''' <typeparam name="ParentT">親の型</typeparam>
        ''' <param name="source">読み込み起点となるインスタンス</param>
        ''' <param name="parentProps">親プロパティリスト</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function CreateParentQuery_Multi(Of MyT, ParentT)(source As QueryableList(Of MyT), parentProps As List(Of PropertyInfo)) As Query(Of ParentT)
            'プロパティごとに処理
            Dim subq As IEnumerable(Of ParentT) = (From el In parentProps Select CreateParentQuery_Single(Of MyT, ParentT)(source.InnerList, el)).ToArray
            Return source.From(Of ParentT).WhereOR(subq)
        End Function
#End Region

#Region "LoadChild"
        ''' <summary>
        ''' 子を読み込みます
        ''' </summary>
        ''' <typeparam name="MyT">自身の型</typeparam>
        ''' <typeparam name="ChildT">子の型</typeparam>
        ''' <param name="source">読み込み起点となるインスタンス</param>
        ''' <param name="childCallback">コールバック関数</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        <Extension()>
        Public Function LoadChild(Of MyT, ChildT)(source As QueryableList(Of MyT), Optional childCallback As Action(Of IList) = Nothing) As QueryableList(Of MyT)
            '検索条件を動的に作成
            Dim rels As List(Of ChildRelation) = (From el In GetType(MyT).ToTable.ChildRelations Where el.ChildType.Equals(GetType(ChildT))).ToList
            Dim q As Query(Of ChildT) = CreateChildQuery(Of MyT, ChildT)(source, rels)
            q.Provider.Loader.MappingIgnoreProperty = rels.First.YourColumn.PropertyInfo

            'マッピング実行
            Dim lst As List(Of ChildT) = q.ToList

            '親リストのコールバック
            If childCallback IsNot Nothing Then
                childCallback.Invoke(lst)
            Else
                '結合
                PropertyWriter.ClearAndAddChild(Of MyT, ChildT)(source.InnerList, lst, rels.First.CollectionProperty.Name)
            End If

            '返却
            Return source
        End Function

        ''' <summary>
        ''' 子を読み込みクエリーを生成します
        ''' </summary>
        ''' <typeparam name="MyT">自身の型</typeparam>
        ''' <typeparam name="ChildT">子の型</typeparam>
        ''' <param name="source">読み込み起点となるインスタンス</param>
        ''' <param name="rels">結合条件</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function CreateChildQuery(Of MyT, ChildT)(source As QueryableList(Of MyT), rels As IEnumerable(Of ChildRelation)) As Query(Of ChildT)
            Dim q As Query(Of ChildT) = source.From(Of ChildT)()

            If rels.Count = 1 Then
                '結合条件が単一の場合
                Dim rel = rels.First
                q.WhereInOrEqual(rel.YourColumn.PropertyInfo, (From el In source.InnerList Select rel.MyColumn.GetValue(el) Distinct).ToArray)
            Else
                '結合条件が複数の場合
                Dim fn As Func(Of MyT, Query(Of ChildT)) =
                    Function(x)
                        Dim subq As Query(Of ChildT) = source.From(Of ChildT)()
                        For Each rel As ChildRelation In rels
                            subq.WhereEquals(rel.YourColumn.PropertyInfo, rel.MyColumn.GetValue(x))
                        Next
                        Return subq
                    End Function

                q.WhereOR((From el In source.InnerList Select fn.Invoke(el)).ToArray)
            End If

            Return q
        End Function
#End Region

    End Module

End Namespace